package problem;

import java.awt.Color;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;

import javafx.concurrent.Task;

import org.apache.commons.math3.stat.StatUtils;
import org.graphstream.algorithm.BetweennessCentrality;
import org.graphstream.algorithm.ConnectedComponents;
import org.graphstream.algorithm.Spectrum;
import org.graphstream.algorithm.Toolkit;
import org.graphstream.algorithm.coloring.WelshPowell;
import org.graphstream.algorithm.measure.ClosenessCentrality;
import org.graphstream.algorithm.measure.EigenvectorCentrality;
import org.graphstream.graph.Graph;
import org.graphstream.graph.Node;
import org.graphstream.graph.implementations.SingleGraph;

import Jama.EigenvalueDecomposition;
import Jama.Matrix;

public class GraphColoringEPM extends Task<Void> {
	private int infinity=2147483647;
	private PrintWriter writer;
	private List<File> sourceFileList;
	File directory;

	private Graph graph;

	private boolean numberOfNodes;
	private boolean numberOfEdges;
	private boolean calculateNodesEdges;
	private boolean calculateEdgesNodes;
	private boolean calculateDensity;
	private double nodesEdges;
	private double edgesNodes;
	private double density;

	private boolean calculateMinDegree;
	private boolean calculateMaxDegree;
	private boolean calculateMeanDegree;
	private boolean calculateStdDegree;
	private boolean calculateVCDegree;
	private boolean calculateMedianDegree;
	private boolean calculateQ1Degree;
	private boolean calculateQ3Degree;
	private boolean degreeCalculated;
	private double minDegree;
	private double maxDegree;
	private double meanDegree;
	private double stdDegree;
	private double vCDegree;
	private double medianDegree;
	private double q1Degree;
	private double q3Degree;

	private boolean calculateMinBtwnsCentrality;
	private boolean calculateMaxBtwnsCentrality;
	private boolean calculateMeanBtwnsCentrality;
	private boolean calculateStdBtwnsCentrality;
	private boolean calculateVCBtwnsCentrality;
	private boolean calculateMedianBtwns;
	private boolean calculateQ1Btwns;
	private boolean calculateQ3Btwns;
	private boolean btwnsCalculated;
	private double minBtwnsCentrality;
	private double maxBtwnsCentrality;
	private double meanBtwnsCentrality;
	private double stdBtwnsCentrality;
	private double vCBtwnsCentrality;
	private double medianBtwnsCentrality;
	private double q1BtwnsCentrality;
	private double q3BtwnsCentrality;

	private boolean calculateMinCloseness;
	private boolean calculateMaxCloseness;
	private boolean calculateMeanCloseness;
	private boolean calculateStdCloseness;
	private boolean calculateVCCloseness;
	private boolean calculateMedianCloseness;
	private boolean calculateQ1Closeness;
	private boolean calculateQ3Closeness;
	private boolean closenessCalculated;
	private double minCloseness;
	private double maxCloseness;
	private double meanCloseness;
	private double stdCloseness;
	private double vCCloseness;
	private double medianCloseness;
	private double q1Closeness;
	private double q3Closeness;

	private boolean calculateMinEgvCentrality;
	private boolean calculateMaxEgvCentrality;
	private boolean calculateMeanEgvCentrality;
	private boolean calculateStdEgvCentrality;
	private boolean calculateVCEgvCentrality;
	private boolean calculateMedianEgvCentrality;
	private boolean calculateQ1EgvCentrality;
	private boolean calculateQ3EgvCentrality;
	private boolean egvCentralityCalculated;
	private double minEgvCentrality;
	private double maxEgvCentrality;
	private double meanEgvCentrality;
	private double stdEgvCentrality;
	private double vCEgvCentrality;
	private double medianEgvCentrality;
	private double q1EgvCentrality;
	private double q3EgvCentrality;

	private boolean calculateMinEccentricity;
	private boolean calculateMaxEccentricity;
	private boolean calculateMeanEccentricity;
	private boolean calculateStdEccentricity;
	private boolean calculateVCEccentricity;
	private boolean calculateMedianEccentricity;
	private boolean calculateQ1Eccentricity;
	private boolean calculateQ3Eccentricity;
	private boolean eccentricityCalculated;
	private double minEccentricity;
	private double maxEccentricity;
	private double meanEccentricity;
	private double stdEccentricity;
	private double vCEccentricity;
	private double medianEccentricity;
	private double q1Eccentricity;
	private double q3Eccentricity;

	private boolean calculateMinLocalClustering;
	private boolean calculateMaxLocalClustering;
	private boolean calculateMeanLocalClustering;
	private boolean calculateStdLocalClustering;
	private boolean calculateVCLocalClustering;
	private boolean calculateMedianClustering;
	private boolean calculateQ1Clustering;
	private boolean calculateQ3Clustering;
	private boolean localClusteringCalculated;
	private double minLocalClustering;
	private double maxLocalClustering;
	private double meanLocalClustering;
	private double stdLocalClustering;
	private double vCLocalClustering;
	private double medianClustering;
	private double q1Clustering;
	private double q3Clustering;

	private boolean calculateMinLocalWClustering;
	private boolean calculateMaxLocalWClustering;
	private boolean calculateMeanLocalWClustering;
	private boolean calculateStdLocalWClustering;
	private boolean calculateVCLocalWClustering;
	private boolean calculateMedianWClustering;
	private boolean calculateQ1WClustering;
	private boolean calculateQ3WClustering;
	private boolean localWClusteringCalculated;
	private double minLocalWClustering;
	private double maxLocalWClustering;
	private double meanLocalWClustering;
	private double stdLocalWClustering;
	private double vCLocalWClustering;
	private double medianWClustering;
	private double q1WClustering;
	private double q3WClustering;

	private boolean calculateLargestAdjEgv;
	private boolean calculateSecondLargestAdjEgv;
	private boolean calculateSecondSmallestAdjEgv;
	private boolean calculateSmallestAdjEgv;
	private boolean calculateMeanSpectrum;
	private boolean calculateStdSpectrum;
	private boolean calculateVCSpectrum;
	private boolean calculateEnergy;
	private boolean gapLargestAnd2ndLargestAdjCalculated;
	private double largestAdjEgv;
	private double secondLargestAdjEgv;
	private double secondSmallestAdjEgv;
	private double smallestAdjEgv;
	private double meanSpectrum;
	private double stdSpectrum;
	private double vCSpectrum;
	private double energy;
	private double gapLargestAnd2ndLargestAdj;
	//Eigenvalues calculated on top of Adjacency Matrix
	private double[] spectra;
	private boolean spectraCalculated;

	private boolean calculateLaplacianIndex;
	private boolean calculateSecondLargestLapEgv;
	private boolean calculateSmallestNZLapEgv;
	private boolean calculateSecondSmallestNZLapEgv;
	private boolean calculateAlgConnectivity;
	private boolean calculateSmallestLapEgv;
	private boolean calculateGapLargestAndSmallestNZLap;
	private double laplacianIndex;
	private double secondLargestLapEgv;
	private double smallestNZLapEgv;
	private double secondSmallestNZLapEgv;
	private double algConnectivity;
	private double smallestLapEgv;
	private double gapLargestAndSmallestNZLap;
	private double[][] laplacianMatrix;
	//Laplacian Eigenvalues calculated on top of Laplacian Matrix
	private ArrayList<Double> laplacianEigenvalues;
	private boolean laplacianEigenvaluesCalculated;

	private int[][] distanceMatrix;
	private boolean distanceMatrixBuilt;
	private boolean calculateWienerIndex;
	private double wienerIndex;
	private boolean calculateAvgPathLength;
	private double avgPathLength;
	private boolean calculateGirth;
	private boolean calculateConnectedComponents;
	private boolean connectedComponentsCalculated;
	private boolean calculateRank;
	private boolean calculateCoRank;
	private int girth;
	private int connectedComponents;
	private int rank;
	private int coRank;

	private boolean sizeLargestMaximalCliqueCalculated;
	private double sizeLargestMaximalClique;

	private boolean degeneracy;

	public GraphColoringEPM(){
		numberOfNodes = false;
		numberOfEdges = false;
		calculateNodesEdges = false;
		calculateEdgesNodes = false;
		calculateDensity = false;

		calculateMinDegree = false;
		calculateMaxDegree = false;
		calculateMeanDegree = false;
		calculateStdDegree = false;
		calculateVCDegree = false;
		calculateMedianDegree = false;
		calculateQ1Degree = false;
		calculateQ3Degree = false;

		calculateMinBtwnsCentrality = false;
		calculateMaxBtwnsCentrality = false;
		calculateMeanBtwnsCentrality = false;
		calculateStdBtwnsCentrality = false;
		calculateVCBtwnsCentrality = false;
		calculateMedianBtwns = false;
		calculateQ1Btwns = false;
		calculateQ3Btwns = false;

		calculateMinCloseness = false;
		calculateMaxCloseness = false;
		calculateMeanCloseness = false;
		calculateStdCloseness = false;
		calculateVCCloseness = false;
		calculateMedianCloseness = false;
		calculateQ1Closeness = false;
		calculateQ3Closeness = false;

		calculateMinEgvCentrality = false;
		calculateMaxEgvCentrality = false;
		calculateMeanEgvCentrality = false;
		calculateStdEgvCentrality = false;
		calculateVCEgvCentrality = false;
		calculateMedianEgvCentrality = false;
		calculateQ1EgvCentrality = false;
		calculateQ3EgvCentrality = false;

		calculateMinEccentricity = false;
		calculateMaxEccentricity = false;
		calculateMeanEccentricity = false;
		calculateStdEccentricity = false;
		calculateVCEccentricity = false;
		calculateMedianEccentricity = false;
		calculateQ1Eccentricity = false;
		calculateQ3Eccentricity = false;

		calculateMinLocalClustering = false;
		calculateMaxLocalClustering = false;
		calculateMeanLocalClustering = false;
		calculateStdLocalClustering = false;
		calculateVCLocalClustering = false;
		calculateMedianClustering = false;
		calculateQ1Clustering = false;
		calculateQ3Clustering = false;

		calculateMinLocalWClustering = false;
		calculateMaxLocalWClustering = false;
		calculateMeanLocalWClustering = false;
		calculateStdLocalWClustering = false;
		calculateVCLocalWClustering = false;
		calculateMedianWClustering = false;
		calculateQ1WClustering = false;
		calculateQ3WClustering = false;

		calculateLargestAdjEgv = false;
		calculateSecondLargestAdjEgv = false;
		calculateSecondSmallestAdjEgv = false;
		calculateSmallestAdjEgv = false;
		calculateMeanSpectrum = false;
		calculateStdSpectrum = false;
		calculateVCSpectrum = false;
		calculateEnergy = false;

		calculateLaplacianIndex = false;
		calculateSecondLargestLapEgv = false;
		calculateSmallestNZLapEgv = false;
		calculateSecondSmallestNZLapEgv = false;
		calculateAlgConnectivity = false;
		calculateSmallestLapEgv = false;
		calculateGapLargestAndSmallestNZLap = false;

		calculateWienerIndex = false;
		calculateAvgPathLength = false;

		sizeLargestMaximalCliqueCalculated = false;

		degeneracy = false;
	}

	private void resetCalculation() {
		//Check if it is necessary to calculate Degree features
		if (calculateMinDegree ||
		calculateMaxDegree || 
		calculateMeanDegree	|| 
		calculateStdDegree	|| 
		calculateVCDegree || 
		calculateMedianDegree || 
		calculateQ1Degree	|| 
		calculateQ3Degree)
			degreeCalculated = false;
		else
			degreeCalculated = true;
		
		//Check if it is necessary to calculate Betweenness Centrality features
		if (calculateMinBtwnsCentrality ||
		calculateMaxBtwnsCentrality || 
		calculateMeanBtwnsCentrality || 
		calculateStdBtwnsCentrality || 
		calculateVCBtwnsCentrality || 
		calculateMedianBtwns || 
		calculateQ1Btwns || 
		calculateQ3Btwns)
			btwnsCalculated = false;
		else
			btwnsCalculated = true;
		
		//Check if it is necessary to calculate Closeness features
		if (calculateMinCloseness || 
		calculateMaxCloseness || 
		calculateMeanCloseness ||
		calculateStdCloseness || 
		calculateVCCloseness || 
		calculateMedianCloseness ||
		calculateQ1Closeness ||	
		calculateQ3Closeness)
			closenessCalculated = false;
		else
			closenessCalculated = true;
		
		//Check if it is necessary to calculate Eigenvector Centrality features
		if (calculateMinEgvCentrality ||
		calculateMaxEgvCentrality ||
		calculateMeanEgvCentrality ||
		calculateStdEgvCentrality ||
		calculateVCEgvCentrality ||
		calculateMedianEgvCentrality ||
		calculateQ1EgvCentrality ||
		calculateQ3EgvCentrality)
			egvCentralityCalculated = false;
		else
			egvCentralityCalculated = true;
		
		//Check if it is necessary to calculate Eccentricity features
		if (calculateMinEccentricity ||
		calculateMaxEccentricity ||
		calculateMeanEccentricity ||
		calculateStdEccentricity ||
		calculateVCEccentricity ||
		calculateMedianEccentricity ||
		calculateQ1Eccentricity ||
		calculateQ3Eccentricity)
			eccentricityCalculated = false;
		else
			eccentricityCalculated = true;
		
		//Check if it is necessary to calculate Local Clustering features
		if (calculateMinLocalClustering ||
		calculateMaxLocalClustering ||
		calculateMeanLocalClustering ||
		calculateStdLocalClustering ||
		calculateVCLocalClustering ||
		calculateMedianClustering ||
		calculateQ1Clustering ||
		calculateQ3Clustering)
			localClusteringCalculated = false;
		else
			localClusteringCalculated = true;
		
		//Check if it is necessary to calculate Weighted Local Clustering features
		if (calculateMinLocalWClustering ||
		calculateMaxLocalWClustering ||
		calculateMeanLocalWClustering ||
		calculateStdLocalWClustering ||
		calculateVCLocalWClustering ||
		calculateMedianWClustering ||
		calculateQ1WClustering ||
		calculateQ3WClustering)
			localWClusteringCalculated = false;
		else
			localWClusteringCalculated = true;
		
		if (calculateLargestAdjEgv ||
		calculateSecondLargestAdjEgv ||
		calculateSecondSmallestAdjEgv ||
		calculateSmallestAdjEgv ||
		calculateMeanSpectrum ||
		calculateStdSpectrum ||
		calculateVCSpectrum ||
		calculateEnergy)
			spectraCalculated = false;
		else
			spectraCalculated =  true;
		
		if (calculateLaplacianIndex ||
		calculateSecondLargestLapEgv ||
		calculateSmallestNZLapEgv ||
		calculateSecondSmallestNZLapEgv ||
		calculateAlgConnectivity ||
		calculateSmallestLapEgv ||
		calculateGapLargestAndSmallestNZLap)
			laplacianEigenvaluesCalculated = false;
		else
			laplacianEigenvaluesCalculated = true;
		
		connectedComponentsCalculated = false;
		
		//Reset matrices
		spectra = new double[0];
		laplacianMatrix = new double[0][0];
		laplacianEigenvalues = new ArrayList<Double>();
		distanceMatrix = new int[0][0];
		distanceMatrixBuilt = false;
	}

	//Graph Coloring Empirical Performance Model Generation Task that can be called by a Thread
	@Override
	protected Void call() throws Exception {
		//Create ARFF file and write their headers
		writeARFFHeader(directory.getAbsolutePath()+"/graph-properties.arff");

		//Start processing instances
		updateProgress(0,sourceFileList.size());
		for (int i=0; i<sourceFileList.size();i++){
			resetCalculation();
			readDIMACS(sourceFileList.get(i).getPath());
			writeARFF(sourceFileList.get(i).getName());
			updateProgress(i+1,sourceFileList.size());
		}
		return null;
	}

	//Load DIMACS Graph Coloring Problem Standard Format (.COL)
	private void readDIMACS(String filePath){
		try {
			BufferedReader reader = new BufferedReader(new FileReader(filePath));
			String line = null;
			while ((line = reader.readLine()) != null) {
				switch (line.charAt(0)) {
				case 'p':
					graph = new SingleGraph(filePath);
					break;
				case 'e':
				{
					String[] arguments = line.split(" ");
					String id1 = new String(arguments[1].concat("-").concat(arguments[2]));
					String id2 = new String(arguments[2].concat("-").concat(arguments[1]));
					if (graph.getNode(arguments[1]) == null)
						graph.addNode(arguments[1]);
					if (graph.getNode(arguments[2]) == null)
						graph.addNode(arguments[2]);
					if (graph.getEdge(id1) == null && graph.getEdge(id2)==null)
						graph.addEdge(id1,arguments[1],arguments[2],false);
					break;
				}
				default:
					break;
				}
			}
			reader.close();
		} catch (Exception e) {
			System.out.println("Exception when reading DIMACS file");
		}
	}

	private void writeARFFHeader(String filePath) {
		try {
			writer = new PrintWriter(new BufferedWriter(new FileWriter(filePath)));
			writer.println("% 1. Title: Graph Coloring Empirical Performance Model");
			writer.println("%");
			writer.println("% 2. Sources:");
			writer.println("%	(a) File automatically generated by Metalearner");
			writer.println("% 	(b) Date: ");
			writer.println("%");
			writer.println("@RELATION coloringepm");
			writer.println();
			if (numberOfNodes)
				writer.println("@ATTRIBUTE numberofnodes NUMERIC");
			if (numberOfEdges)
				writer.println("@ATTRIBUTE numberofedges NUMERIC");
			if (calculateNodesEdges)
				writer.println("@ATTRIBUTE rationodesedges NUMERIC");
			if (calculateEdgesNodes)
				writer.println("@ATTRIBUTE ratioedgesnodes NUMERIC");
			if (calculateDensity)
				writer.println("@ATTRIBUTE density NUMERIC");

			//Write ARFF Header for Degree Features
			if (calculateMinDegree)
				writer.println("@ATTRIBUTE mindegree NUMERIC");
			if (calculateMaxDegree)
				writer.println("@ATTRIBUTE maxdegree NUMERIC");
			if (calculateMeanDegree)
				writer.println("@ATTRIBUTE meandegree NUMERIC");
			if (calculateStdDegree)
				writer.println("@ATTRIBUTE stddegree NUMERIC");
			if (calculateVCDegree)
				writer.println("@ATTRIBUTE vcdegree NUMERIC");
			if (calculateMedianDegree)
				writer.println("@ATTRIBUTE mediandegree NUMERIC");
			if (calculateQ1Degree)
				writer.println("@ATTRIBUTE q1degree NUMERIC");
			if (calculateQ3Degree)
				writer.println("@ATTRIBUTE q3degree NUMERIC");

			//Write ARFF Header for Betweenness Centrality Features
			if (calculateMinBtwnsCentrality)
				writer.println("@ATTRIBUTE minbtwns NUMERIC");
			if (calculateMaxBtwnsCentrality)
				writer.println("@ATTRIBUTE maxbtwns NUMERIC");
			if (calculateMeanBtwnsCentrality)
				writer.println("@ATTRIBUTE meanbtwns NUMERIC");
			if (calculateStdBtwnsCentrality)
				writer.println("@ATTRIBUTE stdbtwns NUMERIC");
			if (calculateVCBtwnsCentrality)
				writer.println("@ATTRIBUTE vcbtwns NUMERIC");
			if (calculateMedianBtwns)
				writer.println("@ATTRIBUTE medianbtwns NUMERIC");
			if (calculateQ1Btwns)
				writer.println("@ATTRIBUTE q1btwns NUMERIC");
			if (calculateQ3Btwns)
				writer.println("@ATTRIBUTE q3btwns NUMERIC");

			//Write ARFF Header for Closeness Centrality Features
			if (calculateMinCloseness)
				writer.println("@ATTRIBUTE mincloseness NUMERIC");
			if (calculateMaxCloseness)
				writer.println("@ATTRIBUTE maxcloseness NUMERIC");
			if (calculateMeanCloseness)
				writer.println("@ATTRIBUTE meancloseness NUMERIC");
			if (calculateStdCloseness)
				writer.println("@ATTRIBUTE stdcloseness NUMERIC");
			if (calculateVCCloseness)
				writer.println("@ATTRIBUTE vccloseness NUMERIC");
			if (calculateMedianCloseness)
				writer.println("@ATTRIBUTE mediancloseness NUMERIC");
			if (calculateQ1Closeness)
				writer.println("@ATTRIBUTE q1closeness NUMERIC");
			if (calculateQ3Closeness)
				writer.println("@ATTRIBUTE q3closeness NUMERIC");

			//Write ARFF Header for Eigenvector Centrality Features
			if (calculateMinEgvCentrality)
				writer.println("@ATTRIBUTE minegvcentrality NUMERIC");
			if (calculateMaxEgvCentrality)
				writer.println("@ATTRIBUTE maxegvcentrality NUMERIC");
			if (calculateMeanEgvCentrality)
				writer.println("@ATTRIBUTE meanegvcentrality NUMERIC");
			if (calculateStdEgvCentrality)
				writer.println("@ATTRIBUTE stdegvcentrality NUMERIC");
			if (calculateVCEgvCentrality)
				writer.println("@ATTRIBUTE vcegvcentrality NUMERIC");
			if (calculateMedianEgvCentrality)
				writer.println("@ATTRIBUTE medianegvcentrality NUMERIC");
			if (calculateQ1EgvCentrality)
				writer.println("@ATTRIBUTE q1egvcentrality NUMERIC");
			if (calculateQ3EgvCentrality)
				writer.println("@ATTRIBUTE q3egvcentrality NUMERIC");

			//Write ARFF Header for Eccentricity
			if (calculateMinEccentricity)
				writer.println("@ATTRIBUTE mineccentricity NUMERIC");
			if (calculateMaxEccentricity)
				writer.println("@ATTRIBUTE maxeccentricity NUMERIC");
			if (calculateMeanEccentricity)
				writer.println("@ATTRIBUTE meaneccentricity NUMERIC");
			if (calculateStdEccentricity)
				writer.println("@ATTRIBUTE stdeccentricity NUMERIC");
			if (calculateVCEccentricity)
				writer.println("@ATTRIBUTE vceccentricity NUMERIC");
			if (calculateMedianEccentricity)
				writer.println("@ATTRIBUTE medianeccentricity NUMERIC");
			if (calculateQ1Eccentricity)
				writer.println("@ATTRIBUTE q1eccentricity NUMERIC");
			if (calculateQ3Eccentricity)
				writer.println("@ATTRIBUTE q3eccentricity NUMERIC");

			//Write ARFF Header for Local Clustering Coefficient
			if (calculateMinLocalClustering)
				writer.println("@ATTRIBUTE minlocalclustering NUMERIC");
			if (calculateMaxLocalClustering)
				writer.println("@ATTRIBUTE maxlocalclustering NUMERIC");
			if (calculateMeanLocalClustering)
				writer.println("@ATTRIBUTE meanlocalclustering NUMERIC");
			if (calculateStdLocalClustering)
				writer.println("@ATTRIBUTE stdlocalclustering NUMERIC");
			if (calculateVCLocalClustering)
				writer.println("@ATTRIBUTE vclocalclustering NUMERIC");
			if (calculateMedianClustering)
				writer.println("@ATTRIBUTE medianclustering NUMERIC");
			if (calculateQ1Clustering)
				writer.println("@ATTRIBUTE q1clustering NUMERIC");
			if (calculateQ3Clustering)
				writer.println("@ATTRIBUTE q3clustering NUMERIC");

			//Write ARFF Header for Local Weighted Clustering Coefficient
			if (calculateMinLocalWClustering)
				writer.println("@ATTRIBUTE minlocalwclustering NUMERIC");
			if (calculateMaxLocalWClustering)
				writer.println("@ATTRIBUTE maxlocalwclustering NUMERIC");
			if (calculateMeanLocalWClustering)
				writer.println("@ATTRIBUTE meanlocalwclustering NUMERIC");
			if (calculateStdLocalWClustering)
				writer.println("@ATTRIBUTE stdlocalwclustering NUMERIC");
			if (calculateVCLocalWClustering)
				writer.println("@ATTRIBUTE vclocalwclustering NUMERIC");
			if (calculateMedianWClustering)
				writer.println("@ATTRIBUTE medianwclustering NUMERIC");
			if (calculateQ1WClustering)
				writer.println("@ATTRIBUTE q1wclustering NUMERIC");
			if (calculateQ3WClustering)
				writer.println("@ATTRIBUTE q3wclustering NUMERIC");

			//Write ARFF Header for Graph Spectra
			if (calculateLargestAdjEgv)
				writer.println("@ATTRIBUTE index NUMERIC");
			if (calculateSecondLargestAdjEgv)
				writer.println("@ATTRIBUTE seclargestadjegv NUMERIC");
			if (calculateSecondSmallestAdjEgv)
				writer.println("@ATTRIBUTE secsmallestadjegv NUMERIC");
			if (calculateSmallestAdjEgv)
				writer.println("@ATTRIBUTE smallestadjegv NUMERIC");
			if (calculateMeanSpectrum)
				writer.println("@ATTRIBUTE meanspectrum NUMERIC");
			if (calculateStdSpectrum)
				writer.println("@ATTRIBUTE stdspectrum NUMERIC");
			if (calculateVCSpectrum)
				writer.println("@ATTRIBUTE vcspectrum NUMERIC");
			if (calculateEnergy)
				writer.println("@ATTRIBUTE energy NUMERIC");
			if (gapLargestAnd2ndLargestAdjCalculated)
				writer.println("@ATTRIBUTE gaplargestand2ndlargestadj NUMERIC");

			if (calculateLaplacianIndex)
				writer.println("@ATTRIBUTE laplacianindex NUMERIC");
			if (calculateSecondLargestLapEgv)
				writer.println("@ATTRIBUTE seclargestlapegv NUMERIC");
			if (calculateSmallestNZLapEgv)
				writer.println("@ATTRIBUTE smallestnzlapegv NUMERIC");
			if (calculateSecondSmallestNZLapEgv)
				writer.println("@ATTRIBUTE secondsmallestnzlapegv NUMERIC");
			if (calculateAlgConnectivity)
				writer.println("@ATTRIBUTE algconnectivity NUMERIC");
			if (calculateSmallestLapEgv)
				writer.println("@ATTRIBUTE smallestlapegv NUMERIC");
			if (calculateGapLargestAndSmallestNZLap)
				writer.println("@ATTRIBUTE gaplargestandsmallestnzlap NUMERIC");

			if (calculateWienerIndex)
				writer.println("@ATTRIBUTE wienerindex NUMERIC");
			if (calculateAvgPathLength)
				writer.println("@ATTRIBUTE avgpathlength NUMERIC");
			if (calculateGirth)
				writer.println("@ATTRIBUTE girth NUMERIC");
			if (calculateConnectedComponents)
				writer.println("@ATTRIBUTE connectedcomponents NUMERIC");
			if (calculateRank)
				writer.println("@ATTRIBUTE rank NUMERIC");
			if (calculateCoRank)
				writer.println("@ATTRIBUTE corank NUMERIC");

			if (sizeLargestMaximalCliqueCalculated)
				writer.println("@ATTRIBUTE sizelargestmaximalclique NUMERIC");
			if (degeneracy)
				writer.println("@ATTRIBUTE degeneracy NUMERIC");

			writer.println("@ATTRIBUTE class NUMERIC");
			writer.println();
			writer.println("@DATA");
			writer.close();
		} catch (FileNotFoundException e) {
			System.out.println("File does not exists");
			e.printStackTrace();
		} catch (UnsupportedEncodingException e) {
			System.out.println("Unsupported Encoding");
			e.printStackTrace();
		} catch (IOException e) {
			System.out.println("IO Exception");
			e.printStackTrace();
		}
	}

	private void writeFeatures(String instanceName) {
		writer.print(instanceName+",");
		if (numberOfNodes)
			writer.print(graph.getNodeCount()+",");
		if (numberOfEdges)
			writer.print(graph.getEdgeCount()+",");
		if (calculateNodesEdges)
			writer.print(nodesEdges+",");
		if (calculateEdgesNodes)
			writer.print(edgesNodes+",");
		if (calculateDensity)
			writer.print(density+",");

		//Write Degree Features
		if (calculateMinDegree)
			writer.print(minDegree+",");
		if (calculateMaxDegree)
			writer.print(maxDegree+",");
		if (calculateMeanDegree)
			writer.print(meanDegree+",");
		if (calculateStdDegree)
			writer.print(stdDegree+",");
		if (calculateVCDegree)
			writer.print(vCDegree+",");
		if (calculateMedianDegree)
			writer.print(medianDegree+",");
		if (calculateQ1Degree)
			writer.print(q1Degree+",");
		if (calculateQ3Degree)
			writer.print(q3Degree+",");

		//Write Betweenness Centrality Features
		if (calculateMinBtwnsCentrality)
			writer.print(minBtwnsCentrality+",");
		if (calculateMaxBtwnsCentrality)
			writer.print(maxBtwnsCentrality+",");
		if (calculateMeanBtwnsCentrality)
			writer.print(meanBtwnsCentrality+",");
		if (calculateStdBtwnsCentrality)
			writer.print(stdBtwnsCentrality+",");
		if (calculateVCBtwnsCentrality)
			writer.print(vCBtwnsCentrality+",");
		if (calculateMedianBtwns)
			writer.print(medianBtwnsCentrality+",");
		if (calculateQ1Btwns)
			writer.print(q1BtwnsCentrality+",");
		if (calculateQ3Btwns)
			writer.print(q3BtwnsCentrality+",");

		if (calculateMinCloseness)
			writer.print(minCloseness+",");
		if (calculateMaxCloseness)
			writer.print(maxCloseness+",");
		if (calculateMeanCloseness)
			writer.print(meanCloseness+",");
		if (calculateStdCloseness)
			writer.print(stdCloseness+",");
		if (calculateVCCloseness)
			writer.print(vCCloseness+",");
		if (calculateMedianCloseness)
			writer.print(medianCloseness+",");
		if (calculateQ1Closeness)
			writer.print(q1Closeness+",");
		if (calculateQ3Closeness)
			writer.print(q3Closeness+",");

		if (calculateMinEgvCentrality)
			writer.print(minEgvCentrality+",");
		if (calculateMaxEgvCentrality)
			writer.print(maxEgvCentrality+",");
		if (calculateMeanEgvCentrality)
			writer.print(meanEgvCentrality+",");
		if (calculateStdEgvCentrality)
			writer.print(stdEgvCentrality+",");
		if (calculateVCEgvCentrality)
			writer.print(vCEgvCentrality+",");
		if (calculateMedianEgvCentrality)
			writer.print(medianEgvCentrality+",");
		if (calculateQ1EgvCentrality)
			writer.print(q1EgvCentrality+",");
		if (calculateQ3EgvCentrality)
			writer.print(q3EgvCentrality+",");

		if (calculateMinEccentricity)
			writer.print(minEccentricity+",");
		if (calculateMaxEccentricity)
			writer.print(maxEccentricity+",");
		if (calculateMeanEccentricity)
			writer.print(meanEccentricity+",");
		if (calculateStdEccentricity)
			writer.print(stdEccentricity+",");
		if (calculateVCEccentricity)
			writer.print(vCEccentricity+",");
		if (calculateMedianEccentricity)
			writer.print(medianEccentricity+",");
		if (calculateQ1Eccentricity)
			writer.print(q1Eccentricity+",");
		if (calculateQ3Eccentricity)
			writer.print(q3Eccentricity+",");

		//Write Data for Local Clustering
		if (calculateMinLocalClustering)
			writer.print(minLocalClustering+",");
		if (calculateMaxLocalClustering)
			writer.print(maxLocalClustering+",");
		if (calculateMeanLocalClustering)
			writer.print(meanLocalClustering+",");
		if (calculateStdLocalClustering)
			writer.print(stdLocalClustering+",");
		if (calculateVCLocalClustering)
			writer.print(vCLocalClustering+",");
		if (calculateMedianClustering)
			writer.print(medianClustering+",");
		if (calculateQ1Clustering)
			writer.print(q1Clustering+",");
		if (calculateQ3Clustering)
			writer.print(q3Clustering+",");

		//Write Data for Weighted Local Clustering Coefficient
		if (calculateMinLocalWClustering)
			writer.print(minLocalWClustering+",");
		if (calculateMaxLocalWClustering)
			writer.print(maxLocalWClustering+",");
		if (calculateMeanLocalWClustering)
			writer.print(meanLocalWClustering+",");
		if (calculateStdLocalWClustering)
			writer.print(stdLocalWClustering+",");
		if (calculateVCLocalWClustering)
			writer.print(vCLocalWClustering+",");
		if (calculateMedianWClustering)
			writer.print(medianWClustering+",");
		if (calculateQ1WClustering)
			writer.print(q1WClustering+",");
		if (calculateQ3WClustering)
			writer.print(q3WClustering+",");

		if (calculateLargestAdjEgv)
			writer.print(largestAdjEgv+",");
		if (calculateSecondLargestAdjEgv)
			writer.print(secondLargestAdjEgv+",");
		if (calculateSecondSmallestAdjEgv)
			writer.print(secondSmallestAdjEgv+",");
		if (calculateSmallestAdjEgv)
			writer.print(smallestAdjEgv+",");
		if (calculateMeanSpectrum)
			writer.print(meanSpectrum+",");
		if (calculateStdSpectrum)
			writer.print(stdSpectrum+",");
		if (calculateVCSpectrum)
			writer.print(vCSpectrum+",");
		if (calculateEnergy)
			writer.print(energy+",");
		if (gapLargestAnd2ndLargestAdjCalculated)
			writer.print(gapLargestAnd2ndLargestAdj+",");

		if (calculateLaplacianIndex)
			writer.print(laplacianIndex+",");
		if (calculateSecondLargestLapEgv)
			writer.print(secondLargestLapEgv+",");
		if (calculateSmallestNZLapEgv)
			writer.print(smallestNZLapEgv+",");
		if (calculateSecondSmallestNZLapEgv)
			writer.print(secondSmallestNZLapEgv+",");
		if (calculateAlgConnectivity)
			writer.print(algConnectivity+",");
		if (calculateSmallestLapEgv)
			writer.print(smallestLapEgv+",");
		if (calculateGapLargestAndSmallestNZLap)
			writer.print(gapLargestAndSmallestNZLap+",");

		if (calculateWienerIndex)
			writer.print(wienerIndex+",");
		if (calculateAvgPathLength)
			writer.print(avgPathLength+",");
		if (calculateGirth)
			writer.print(girth+",");
		if (calculateConnectedComponents)
			writer.print(connectedComponents+",");
		if (calculateRank)
			writer.print(rank+",");
		if (calculateCoRank)
			writer.print(coRank+",");

		if (sizeLargestMaximalCliqueCalculated)
			writer.print(sizeLargestMaximalClique+",");
		if (degeneracy)
			writer.print(computeDegeneracy()+",");

	}

	private void writeARFF(String instanceName){
		//Compute Features for the Graph
		computeFeatures();
		try{
			writer = new PrintWriter(new BufferedWriter(new FileWriter(directory.getAbsolutePath()+"/graph-properties.arff", true)));
			writeFeatures(instanceName);
			writer.println("<class>");
			writer.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	private void computeFeatures(){

		if (calculateNodesEdges)
			nodesEdges = ((double) graph.getNodeCount()/ (double) graph.getEdgeCount());
		if (calculateEdgesNodes)
			edgesNodes = ((double) graph.getEdgeCount()/ (double) graph.getNodeCount());
		if (calculateDensity)
			density = computeDensity();

		if (!degreeCalculated)
			computeDegreeFeatures();

		if (!btwnsCalculated)
			computeBtwnsCentrality();

		if (!closenessCalculated)
			computeCloseness();
		
		if (!egvCentralityCalculated)
			computeEigenvectorCentrality();

		if (!eccentricityCalculated)
			computeEccentricity();

		if (!localClusteringCalculated)
			computeLocalClustering();

		if (!localWClusteringCalculated)
			computeWLocalClustering();

		if (!spectraCalculated)
			computeGraphSpectra();

		if (!laplacianEigenvaluesCalculated)
			computeLaplacianEigenvalues();

		if (calculateWienerIndex)
			computeWienerIndex();

		if (calculateAvgPathLength)
			computeAvgPathLength();
		
		if (calculateGirth)
			computeGirth();
		
		if (calculateConnectedComponents)
			computeConnectedComponents();
		
		if (calculateRank)
		{
			if (!connectedComponentsCalculated)
				computeConnectedComponents();
			computeRank();
		}
		
		if (calculateCoRank)
		{
			if (!connectedComponentsCalculated)
				computeConnectedComponents();
			computeCoRank();
		}

		if (sizeLargestMaximalCliqueCalculated)
			computeSizeLargestMaximalClique();
		
		if (degeneracy)
			computeDegeneracy();

	}

	//Setters #####################################################################################################

	public void setNumberOfNodes(boolean numberOfNodes) {
		this.numberOfNodes = numberOfNodes;
	}

	public void setNumberOfEdges(boolean numberOfEdges) {
		this.numberOfEdges = numberOfEdges;
	}

	public void setNodesEdges(boolean nodesEdges) {
		this.calculateNodesEdges = nodesEdges;
	}

	public void setEdgesNodes(boolean edgesNodes) {
		this.calculateEdgesNodes = edgesNodes;
	}

	public void setMinDegree(boolean minDegree) {
		this.calculateMinDegree = minDegree;
	}

	public void setMaxDegree(boolean maxDegree) {
		this.calculateMaxDegree = maxDegree;
	}

	public void setMeanDegree(boolean meanDegree) {
		this.calculateMeanDegree = meanDegree;
	}

	public void setStdDegree(boolean stdDegree) {
		this.calculateStdDegree = stdDegree;
	}

	public void setVCDegree(boolean vCDegree) {
		this.calculateVCDegree = vCDegree;
	}

	public void setMedianDegree(boolean medianDegree) {
		this.calculateMedianDegree = medianDegree;
	}

	public void setQ1Degree(boolean q1Degree) {
		this.calculateQ1Degree = q1Degree;
	}

	public void setQ3Degree(boolean q3Degree) {
		this.calculateQ3Degree = q3Degree;
	}

	public void setDensity(boolean density) {
		this.calculateDensity = density;
	}

	public void setMinBtwnsCentrality(boolean minBtwnsCentrality) {
		this.calculateMinBtwnsCentrality = minBtwnsCentrality;
	}

	public void setMaxBtwnsCentrality(boolean maxBtwnsCentrality) {
		this.calculateMaxBtwnsCentrality = maxBtwnsCentrality;
	}

	public void setMeanBtwnsCentrality(boolean meanBtwnsCentrality) {
		this.calculateMeanBtwnsCentrality = meanBtwnsCentrality;
	}

	public void setStdBtwnsCentrality(boolean stdBtwnsCentrality) {
		this.calculateStdBtwnsCentrality = stdBtwnsCentrality;
	}

	public void setVCBtwnsCentrality(boolean vCBtwnsCentrality) {
		this.calculateVCBtwnsCentrality = vCBtwnsCentrality;
	}

	public void setMedianBtwns(boolean medianBtwns) {
		this.calculateMedianBtwns = medianBtwns;
	}

	public void setQ1Btwns(boolean q1Btwns) {
		this.calculateQ1Btwns = q1Btwns;
	}

	public void setQ3Btwns(boolean q3Btwns) {
		this.calculateQ3Btwns = q3Btwns;
	}

	public void setMinCloseness(boolean minCloseness) {
		this.calculateMinCloseness = minCloseness;
	}

	public void setMaxCloseness(boolean maxCloseness) {
		this.calculateMaxCloseness = maxCloseness;
	}

	public void setMeanCloseness(boolean meanCloseness) {
		this.calculateMeanCloseness = meanCloseness;
	}

	public void setStdCloseness(boolean stdCloseness) {
		this.calculateStdCloseness = stdCloseness;
	}

	public void setVCCloseness(boolean vCCloseness) {
		this.calculateVCCloseness = vCCloseness;
	}

	public void setMedianCloseness(boolean medianCloseness) {
		this.calculateMedianCloseness = medianCloseness;
	}

	public void setQ1Closeness(boolean q1Closeness) {
		this.calculateQ1Closeness = q1Closeness;
	}

	public void setQ3Closeness(boolean q3Closeness) {
		this.calculateQ3Closeness = q3Closeness;
	}

	public void setMinEgvCentrality(boolean minEgvCentrality) {
		this.calculateMinEgvCentrality = minEgvCentrality;
	}

	public void setMaxEgvCentrality(boolean maxEgvCentrality) {
		this.calculateMaxEgvCentrality = maxEgvCentrality;
	}

	public void setMeanEgvCentrality(boolean meanEgvCentrality) {
		this.calculateMeanEgvCentrality = meanEgvCentrality;
	}

	public void setStdEgvCentrality(boolean stdEgvCentrality) {
		this.calculateStdEgvCentrality = stdEgvCentrality;
	}

	public void setVCEgvCentrality(boolean vCEgvCentrality) {
		this.calculateVCEgvCentrality = vCEgvCentrality;
	}

	public void setMedianEgvCentrality(boolean medianEgvCentrality) {
		this.calculateMedianEgvCentrality = medianEgvCentrality;
	}

	public void setQ1EgvCentrality(boolean q1EgvCentrality) {
		this.calculateQ1EgvCentrality = q1EgvCentrality;
	}

	public void setQ3EgvCentrality(boolean q3EgvCentrality) {
		this.calculateQ3EgvCentrality = q3EgvCentrality;
	}

	public void setMinEccentricity(boolean minEccentricity) {
		this.calculateMinEccentricity = minEccentricity;
	}

	public void setMaxEccentricity(boolean maxEccentricity) {
		this.calculateMaxEccentricity = maxEccentricity;
	}

	public void setMeanEccentricity(boolean meanEccentricity) {
		this.calculateMeanEccentricity = meanEccentricity;
	}

	public void setStdEccentricity(boolean stdEccentricity) {
		this.calculateStdEccentricity = stdEccentricity;
	}

	public void setVCEccentricity(boolean vCEccentricity) {
		this.calculateVCEccentricity = vCEccentricity;
	}

	public void setMedianEccentricity(boolean medianEccentricity) {
		this.calculateMedianEccentricity = medianEccentricity;
	}

	public void setQ1Eccentricity(boolean q1Eccentricity) {
		this.calculateQ1Eccentricity = q1Eccentricity;
	}

	public void setQ3Eccentricity(boolean q3Eccentricity) {
		this.calculateQ3Eccentricity = q3Eccentricity;
	}

	public void setMinLocalClustering(boolean minLocalClustering) {
		this.calculateMinLocalClustering = minLocalClustering;
	}

	public void setMaxLocalClustering(boolean maxLocalClustering) {
		this.calculateMaxLocalClustering = maxLocalClustering;
	}

	public void setMeanLocalClustering(boolean meanLocalClustering) {
		this.calculateMeanLocalClustering = meanLocalClustering;
	}

	public void setStdLocalClustering(boolean stdLocalClustering) {
		this.calculateStdLocalClustering = stdLocalClustering;
	}

	public void setVCLocalClustering(boolean vCLocalClustering) {
		this.calculateVCLocalClustering = vCLocalClustering;
	}

	public void setMedianClustering(boolean medianClustering) {
		this.calculateMedianClustering = medianClustering;
	}

	public void setQ1Clustering(boolean q1Clustering) {
		this.calculateQ1Clustering = q1Clustering;
	}

	public void setQ3Clustering(boolean q3Clustering) {
		this.calculateQ3Clustering = q3Clustering;
	}

	public void setMinLocalWClustering(boolean minLocalWClustering) {
		this.calculateMinLocalWClustering = minLocalWClustering;
	}

	public void setMaxLocalWClustering(boolean maxLocalWClustering) {
		this.calculateMaxLocalWClustering = maxLocalWClustering;
	}

	public void setMeanLocalWClustering(boolean meanLocalWClustering) {
		this.calculateMeanLocalWClustering = meanLocalWClustering;
	}

	public void setStdLocalWClustering(boolean stdLocalWClustering) {
		this.calculateStdLocalWClustering = stdLocalWClustering;
	}

	public void setVCLocalWClustering(boolean vCLocalWClustering) {
		this.calculateVCLocalWClustering = vCLocalWClustering;
	}

	public void setMedianWClustering(boolean medianWClustering) {
		this.calculateMedianWClustering = medianWClustering;
	}

	public void setQ1WClustering(boolean q1wClustering) {
		calculateQ1WClustering = q1wClustering;
	}

	public void setQ3WClustering(boolean q3wClustering) {
		calculateQ3WClustering = q3wClustering;
	}

	public void setIndex(boolean index) {
		this.calculateLargestAdjEgv = index;
	}

	public void set2ndLargestAdjEgv(boolean secondLargestAdjEgv){
		this.calculateSecondLargestAdjEgv = secondLargestAdjEgv;
	}

	public void set2ndSmallestAdjEgv(boolean secondSmallestAdjEgv){
		this.calculateSecondSmallestAdjEgv = secondSmallestAdjEgv;
	}

	public void setSmallestAdjEgv(boolean smallestAdjEgv){
		this.calculateSmallestAdjEgv = smallestAdjEgv;
	}

	public void setMeanSpectrum(boolean meanSpectrum){
		this.calculateMeanSpectrum = meanSpectrum;
	}

	public void setStdSpectrum(boolean stdSpectrum){
		this.calculateStdSpectrum = stdSpectrum;
	}

	public void setVCSpectrum(boolean vCSpectrumCalculated){
		this.calculateVCSpectrum = vCSpectrumCalculated;
	}

	public void setEnergy(boolean energy) {
		this.calculateEnergy = energy;
	}
	
	public void setgapLargestAnd2ndLargestAdj(boolean gapLargestAnd2ndLargestAdj) {
		this.gapLargestAnd2ndLargestAdjCalculated = gapLargestAnd2ndLargestAdj;
	}

	public void setLaplacianIndex(boolean laplacianIndex) {
		this.calculateLaplacianIndex = laplacianIndex;
	}

	public void setSecondLargestLapEgv(boolean secondLargestLapEgv) {
		this.calculateSecondLargestLapEgv = secondLargestLapEgv;
	}

	public void setSmallestNZLapEgv(boolean smallestNZLapEgv) {
		this.calculateSmallestNZLapEgv = smallestNZLapEgv;
	}
	
	public void setSecondSmallestNZLapEgv(boolean secondSmallestNZLapEgv) {
		this.calculateSecondSmallestNZLapEgv = secondSmallestNZLapEgv;
	}
	
	public void setAlgConnectivity(boolean algConnectivity) {
		this.calculateAlgConnectivity = algConnectivity;
	}

	public void setSmallestLapEgv(boolean smallestLapEgv) {
		this.calculateSmallestLapEgv = smallestLapEgv;
	}

	public void setgapLargestAndSmallestNZLap(boolean gapLargestAndSmallestNZLap) {
		this.calculateGapLargestAndSmallestNZLap = gapLargestAndSmallestNZLap;
	}
	
	public void setWienerIndex(boolean wienerIndex){
		this.calculateWienerIndex = wienerIndex;
	}

	public void setAvgPathLength(boolean avgPathLength){
		this.calculateAvgPathLength = avgPathLength;
	}
	
	public void setGirth(boolean girth){
		this.calculateGirth = girth;
	}
	
	public void setConnectedComponents(boolean connectedComponents){
		this.calculateConnectedComponents = connectedComponents;
	}
	
	public void setRank(boolean rank){
		this.calculateRank = rank;
	}
	
	public void setCoRank(boolean coRank){
		this.calculateCoRank = coRank;
	}

	public void setSizeLargestMaximalClique(boolean sizeLargestMaximalClique){
		this.sizeLargestMaximalCliqueCalculated = sizeLargestMaximalClique;
	}

	public void setDegeneracy(boolean degeneracy){
		this.degeneracy = degeneracy;
	}

	public void setInstancesFiles(List<File> fileList){
		this.sourceFileList = fileList;
	}

	public void setTargetDirectory(File directory){
		this.directory = directory;
	}


	//Features Calculation ##########################################################################################
	private double computeDensity() {
		double dEdges = 2*graph.getEdgeCount();
		double density = dEdges/(graph.getNodeCount()*(graph.getNodeCount()-1));
		return density;
	}

	//Degree Features
	private void computeDegreeFeatures() {
		double[] degreeArray = new double[graph.getNodeCount()];
		int index = 0;
		minDegree = infinity;
		maxDegree = 0;
		for(Node n : graph.getEachNode() ) {
			minDegree = Math.min(minDegree, n.getDegree());
			maxDegree = Math.max(maxDegree, n.getDegree());
			degreeArray[index] = n.getDegree();
			index++;
		}
		meanDegree = (double) (2.0*graph.getEdgeCount())/ (double) graph.getNodeCount();
		stdDegree = Math.sqrt(StatUtils.variance(degreeArray));
		vCDegree = stdDegree/meanDegree;
		medianDegree = StatUtils.percentile(degreeArray,50);
		q1Degree = StatUtils.percentile(degreeArray, 25);
		q3Degree = StatUtils.percentile(degreeArray, 75);
		degreeCalculated = true;
	}

	//Betwenness Centrality Features
	private void computeBtwnsCentrality() {
		double[] btwnsArray = new double[graph.getNodeCount()];
		int index = 0;
		minBtwnsCentrality = infinity;
		maxBtwnsCentrality = 0;
		BetweennessCentrality bcb = new BetweennessCentrality();
		bcb.setUnweighted();
		bcb.init(graph);
		bcb.compute();
		for(Node n : graph.getEachNode() ) {
			minBtwnsCentrality = Math.min(minBtwnsCentrality, n.getAttribute("Cb"));
			maxBtwnsCentrality = Math.max(maxBtwnsCentrality, n.getAttribute("Cb"));
			btwnsArray[index] = n.getAttribute("Cb");
			index++;
		}
		meanBtwnsCentrality = StatUtils.mean(btwnsArray);
		stdBtwnsCentrality = Math.sqrt(StatUtils.variance(btwnsArray));
		vCBtwnsCentrality = stdBtwnsCentrality/meanBtwnsCentrality;
		medianBtwnsCentrality = StatUtils.percentile(btwnsArray,50);
		q1BtwnsCentrality = StatUtils.percentile(btwnsArray,25);
		q3BtwnsCentrality = StatUtils.percentile(btwnsArray,75);
		btwnsCalculated = true;
	}

	//Closeness Centrality Features
	private void computeCloseness() {
		double[] closenessArray = new double[graph.getNodeCount()];
		int index = 0;
		minCloseness = infinity;
		maxCloseness = 0;
		ClosenessCentrality clc = new ClosenessCentrality("closeness");
		clc.init(graph);
		clc.compute();
		for(Node n : graph.getEachNode() ) {
			minCloseness = Math.min(minCloseness, n.getAttribute("closeness"));
			maxCloseness = Math.max(maxCloseness, n.getAttribute("closeness"));
			closenessArray[index] = n.getAttribute("closeness");
			index++;
		}
		meanCloseness = StatUtils.mean(closenessArray);
		stdCloseness = Math.sqrt(StatUtils.variance(closenessArray));
		vCCloseness = stdCloseness/meanCloseness;
		medianCloseness = StatUtils.percentile(closenessArray,50);
		q1Closeness = StatUtils.percentile(closenessArray,25);
		q3Closeness = StatUtils.percentile(closenessArray,75);
		closenessCalculated = true;
	}

	//Eigenvector Centrality Features
	private void computeEigenvectorCentrality() {
		double[] egvArray = new double[graph.getNodeCount()];
		int index = 0;
		minEgvCentrality = infinity;
		maxEgvCentrality = 0;
		EigenvectorCentrality egv = new EigenvectorCentrality();
		egv.init(graph);
		egv.setCentralityAttribute(EigenvectorCentrality.DEFAULT_ATTRIBUTE_KEY);
		egv.compute();
		for(Node n : graph.getEachNode() ) {
			minEgvCentrality = Math.min(minEgvCentrality, n.getAttribute(EigenvectorCentrality.DEFAULT_ATTRIBUTE_KEY));
			maxEgvCentrality = Math.max(maxEgvCentrality, n.getAttribute(EigenvectorCentrality.DEFAULT_ATTRIBUTE_KEY));
			egvArray[index] = n.getAttribute(EigenvectorCentrality.DEFAULT_ATTRIBUTE_KEY);
			index++;
		}
		meanEgvCentrality = StatUtils.mean(egvArray);
		stdEgvCentrality = Math.sqrt(StatUtils.variance(egvArray));
		vCEgvCentrality = stdEgvCentrality/meanEgvCentrality;
		medianEgvCentrality = StatUtils.percentile(egvArray,50);
		q1EgvCentrality = StatUtils.percentile(egvArray,25);
		q3EgvCentrality = StatUtils.percentile(egvArray,75);
		egvCentralityCalculated = true;
	}

	//Eccentricity Features
	private void computeEccentricity() {
		double[] eccArray = new double[graph.getNodeCount()];
		int index = 0;
		minEccentricity = infinity;
		for(Node n : graph.getEachNode() ) {
			minEccentricity = Math.min(minEccentricity, Toolkit.unweightedEccentricity(n, false));
			eccArray[index] = Toolkit.unweightedEccentricity(n, false);
			index++;
		}
		maxEccentricity = Toolkit.diameter(graph);
		meanEccentricity = StatUtils.mean(eccArray);
		stdEccentricity = Math.sqrt(StatUtils.variance(eccArray));
		vCEccentricity = stdEccentricity/meanEccentricity;
		medianEccentricity = StatUtils.percentile(eccArray,50);
		q1Eccentricity = StatUtils.percentile(eccArray,25);
		q3Eccentricity = StatUtils.percentile(eccArray,75);
		eccentricityCalculated = true;
	}

	//Local Clustering Coefficient
	private void computeLocalClustering() {
		double[] clusteringArray = new double[graph.getNodeCount()];
		int index = 0;
		minLocalClustering = infinity;
		maxLocalClustering = 0;
		for(Node n : graph.getEachNode() ) {
			minLocalClustering = Math.min(minLocalClustering, Toolkit.clusteringCoefficient(n));
			maxLocalClustering = Math.max(maxLocalClustering, Toolkit.clusteringCoefficient(n));
			clusteringArray[index] = Toolkit.clusteringCoefficient(n);
			index++;
		}
		meanLocalClustering = Toolkit.averageClusteringCoefficient(graph);
		stdLocalClustering = Math.sqrt(StatUtils.variance(clusteringArray));
		vCLocalClustering = stdLocalClustering/meanLocalClustering;
		medianClustering = StatUtils.percentile(clusteringArray,50);
		q1Clustering = StatUtils.percentile(clusteringArray,25);
		q3Clustering = StatUtils.percentile(clusteringArray,75);
		localClusteringCalculated = true;
	}

	//Weighted Local Clustering Coefficient
	private void computeWLocalClustering() {
		double[] clusteringArray = new double[graph.getNodeCount()];
		int index = 0;
		minLocalWClustering = infinity;
		maxLocalWClustering = 0;
		for(Node n : graph.getEachNode() ) {
			minLocalWClustering = Math.min(minLocalWClustering, Toolkit.clusteringCoefficient(n)*n.getDegree());
			maxLocalWClustering = Math.max(maxLocalWClustering, Toolkit.clusteringCoefficient(n)*n.getDegree());
			clusteringArray[index] = Toolkit.clusteringCoefficient(n)*n.getDegree();
			index++;
		}
		meanLocalWClustering = StatUtils.mean(clusteringArray);
		stdLocalWClustering = Math.sqrt(StatUtils.variance(clusteringArray));
		vCLocalWClustering = stdLocalWClustering/meanLocalWClustering;
		medianWClustering = StatUtils.percentile(clusteringArray,50);
		q1WClustering = StatUtils.percentile(clusteringArray,25);
		q3WClustering = StatUtils.percentile(clusteringArray,75);
		localWClusteringCalculated = true;
	}

	//Graph Spectra
	//TODO - Check applicability for high-density graph
	private void computeGraphSpectra() {
		/*ArrayList<Double> sortedSpectra = new ArrayList<Double>();
		Spectrum spc = new Spectrum();
		spc.init(graph);
		spc.compute();
		spectra = spc.getEigenvalues();
		for (int i =0; i<spectra.length;i++)
			sortedSpectra.add(spectra[i]);
		Collections.sort(sortedSpectra);
		largestAdjEgv = sortedSpectra.get(sortedSpectra.size()-1);
		secondLargestAdjEgv = sortedSpectra.get(sortedSpectra.size()-2);
		secondSmallestAdjEgv = sortedSpectra.get(1);
		smallestAdjEgv = sortedSpectra.get(0);
		meanSpectrum = StatUtils.mean(spectra);
		stdSpectrum = Math.sqrt(StatUtils.variance(spectra));
		vCSpectrum = stdSpectrum/meanSpectrum;
		energy = StatUtils.sum(spectra);
		gapLargestAnd2ndLargestAdj = largestAdjEgv - secondLargestAdjEgv;*/
		double[][] doubleAdjMatrix = new double[graph.getNodeCount()][graph.getNodeCount()];
		for (int i=0;i<graph.getNodeCount();i++)
			for (int j=0;j<graph.getNodeCount();j++)
				doubleAdjMatrix[i][j] = Toolkit.getAdjacencyMatrix(graph)[i][j];
		Matrix adjMatrix = new Matrix(doubleAdjMatrix);
		ArrayList<Double> sortedSpectra = new ArrayList<Double>();
		EigenvalueDecomposition e = new EigenvalueDecomposition(adjMatrix);
		for (int i=0; i<e.getRealEigenvalues().length;i++)
			sortedSpectra.add(new Double(e.getRealEigenvalues()[i]));
		Collections.sort(sortedSpectra);
		largestAdjEgv = sortedSpectra.get(sortedSpectra.size()-1);
		secondLargestAdjEgv = sortedSpectra.get(sortedSpectra.size()-2);
		secondSmallestAdjEgv = sortedSpectra.get(1);
		smallestAdjEgv = sortedSpectra.get(0);
		meanSpectrum = StatUtils.mean(spectra);
		stdSpectrum = Math.sqrt(StatUtils.variance(spectra));
		vCSpectrum = stdSpectrum/meanSpectrum;
		energy = StatUtils.sum(spectra);
		gapLargestAnd2ndLargestAdj = largestAdjEgv - secondLargestAdjEgv;
		spectraCalculated = true;
	}

	//Laplacian Features
	private void computeLaplacianEigenvalues(){
		int degree = 0;
		int[][] adjacencyMatrix = Toolkit.getAdjacencyMatrix(graph);
		laplacianMatrix = new double[graph.getNodeCount()][graph.getNodeCount()];

		//Build Laplacian Matrix based on Adjacency Matrix
		for (int i=0;i<graph.getNodeCount();i++){
			for (int j=0;j<graph.getNodeCount();j++){
				//Set the values where adjacency matrix is 1 to -1, otherwise the value is set to zero
				if (adjacencyMatrix[i][j]==1)
					laplacianMatrix[i][j]=-1;
				else
					laplacianMatrix[i][j]=0;
				//Sum up values of one row to calculate degree of a vertex based on adjacency matrix
				degree += adjacencyMatrix[i][j];
			}
			//Set the value on the diagonal equal to the degree of the corresponding vertex
			laplacianMatrix[i][i]=degree;
			degree = 0;
		}

		//Calculate Laplacian Eigenvalues
		laplacianEigenvalues = new ArrayList<Double>();
		Matrix graph = new Matrix(laplacianMatrix);
		EigenvalueDecomposition e = new EigenvalueDecomposition(graph);

		for (int i=0; i<e.getRealEigenvalues().length;i++)
			laplacianEigenvalues.add(new Double(e.getRealEigenvalues()[i]));

		Collections.sort(laplacianEigenvalues);
		laplacianIndex = laplacianEigenvalues.get(laplacianEigenvalues.size()-1);
		secondLargestLapEgv = laplacianEigenvalues.get(laplacianEigenvalues.size()-2);
		algConnectivity = laplacianEigenvalues.get(1);
		smallestLapEgv = laplacianEigenvalues.get(0);
		int index = 0;
		smallestNZLapEgv = laplacianEigenvalues.get(index);
		while (index < laplacianEigenvalues.size() && smallestNZLapEgv == 0) {
			index++;
			smallestNZLapEgv = laplacianEigenvalues.get(index);
		}
		index++;
		secondSmallestNZLapEgv = laplacianEigenvalues.get(index);
		while (index < laplacianEigenvalues.size() && secondSmallestNZLapEgv == 0) {
			index++;
			secondSmallestNZLapEgv = laplacianEigenvalues.get(index);
		}
		gapLargestAndSmallestNZLap = laplacianIndex-smallestNZLapEgv;
		laplacianEigenvaluesCalculated = true;
	}

	//Build Distance Matrix based on Adjacency Matrix
	private void buildDistanceMatrix() {

		distanceMatrix = new int[graph.getNodeCount()][graph.getNodeCount()];
		int[][] adjacencyMatrix = Toolkit.getAdjacencyMatrix(graph);

		//Initialize distance matrix with known distances to 1 and unknown distances to infinity
		for (int i=0;i<graph.getNodeCount();i++)
			for (int j=0;j<graph.getNodeCount();j++)
				if (adjacencyMatrix[i][j]==1)
					distanceMatrix[i][j]=1;
				else
					distanceMatrix[i][j]=infinity;

		//Set diagonal of distance matrix to zero.
		for (int i=0;i<graph.getNodeCount();i++)
			distanceMatrix[i][i]=0;

		//Calculate distance for all nodes in the matrix
		for(int k=0;k<graph.getNodeCount();k++)
			for (int i=0;i<graph.getNodeCount();i++)
				for (int j=0;j<graph.getNodeCount();j++)
					if (distanceMatrix[i][k]+distanceMatrix[k][j]>0)
						if (distanceMatrix[i][j]>distanceMatrix[i][k]+distanceMatrix[k][j])
						{
							distanceMatrix[i][j] = distanceMatrix[i][k]+distanceMatrix[k][j];
							distanceMatrix[j][i] = distanceMatrix[i][k]+distanceMatrix[k][j];
						}

		//Set the distance of unreachable nodes to zero. Useful for disconnected graphs.
		/*for (int i=0;i<graph.getNodeCount();i++)
			for (int j=0;j<graph.getNodeCount();j++)
				if(distanceMatrix[i][j]==infinity)
					distanceMatrix[i][j]=0;*/
		distanceMatrixBuilt = true;
	}

	//Wiener Index Feature
	private void computeWienerIndex(){
		double distancematrixsum = 0;

		if (!distanceMatrixBuilt)
			buildDistanceMatrix();

		for (int i=0;i<graph.getNodeCount();i++)
			for (int j=i;j<graph.getNodeCount();j++)
				distancematrixsum += distanceMatrix[i][j];

		wienerIndex = distancematrixsum;
	}

	//Average Path Length Feature
	private void computeAvgPathLength(){
		double distancematrixsum = 0;

		if (!distanceMatrixBuilt)
			buildDistanceMatrix();

		for (int i=0;i<graph.getNodeCount();i++)
			for (int j=i;j<graph.getNodeCount();j++)
				distancematrixsum += 2*distanceMatrix[i][j];

		avgPathLength = (distancematrixsum/((graph.getNodeCount())*(graph.getNodeCount()-1.0)));
	}

	//Girth
	private void computeGirth() {
		/* Best (smallest) cycle length. */ 
		int best = infinity; 
		/* Queue for our BFS. */ 
		Queue<Node> queue = new LinkedList<Node>();
		Node neighbour;
		Node node;
		
		/* Start a BFS from every vertex except the last two (not needed). */ 
		int root = 0; 
		while(root < graph.getNodeCount() - 2 && best > 3) { 

			/* Reset labels. */ 
			for(Node n : graph.getEachNode() ) {
				n.setAttribute("visited", -1);
				n.setAttribute("depth", 0);
			}

			/* Add initial node to the queue. */
			graph.getNode(root).setAttribute("visited", 0);
			queue.add(graph.getNode(root));

			/* Take next item from the queue. */ 
			node = queue.poll();
			int depth = (int) node.getAttribute("depth");
			while(node != null && best > 3 && depth * 2 - 1 < best) {  
				/* Check all neighbours. */
				depth = (int) node.getAttribute("depth");
				depth++;
				Iterator<Node> neighbourIterator = node.getNeighborNodeIterator(); 
				while (neighbourIterator.hasNext()) {
					neighbour = neighbourIterator.next(); 
					/* We haven't seen this neighbour before. */ 
					if((int) neighbour.getAttribute("visited") < 0) {
						neighbour.setAttribute("visited", depth);
						neighbour.setAttribute("depth", depth);
						queue.add(neighbour); 
						/* Cycle with odd number of edges. */ 
					} else if((int) neighbour.getAttribute("visited") == depth - 1) { 
						if(depth * 2 - 1 < best) 
							best = depth * 2 - 1;
						/* Cycle with even number of edges. */ 
					} else if((int) neighbour.getAttribute("visited") == depth) { 
						if(depth * 2 < best) 
							best = depth * 2; 
					}
				} 

				/* Take next item from the queue. */ 
				node = queue.poll(); 
			}

			/* Clear the queue and prepare to start a BFS from a next vertex. */ 
			queue.clear(); 
			root++;
		} 
		/* We don't want any division by zero errors. */ 
		girth =  best > 0 ? best : 1; 
	}

	//Connected Components
	private void computeConnectedComponents(){
		ConnectedComponents cc = new ConnectedComponents();
		cc.init(graph);
		connectedComponents = cc.getConnectedComponentsCount();
		connectedComponentsCalculated = true;
	}
	
	private void computeRank(){
		rank = graph.getNodeCount()-connectedComponents;
	}
	
	private void computeCoRank(){
		coRank = graph.getEdgeCount()-graph.getNodeCount()+connectedComponents;
	}
	
	//Naive method to find size of Largest Maximal Clique
	private void computeSizeLargestMaximalClique() {
		Iterator<List<Node>> iterator = Toolkit.getMaximalCliqueIterator(graph);
		int candidateMaxClique = graph.getNodeCount();
		while (iterator.hasNext())
			if (iterator.next().size() > candidateMaxClique)
				candidateMaxClique = iterator.next().size();
		sizeLargestMaximalClique = candidateMaxClique;
		
//		int *cliqueSize = new int[graph.n];
//
//		double mean = 0;
//
//		for (int node = 0; node < graph.n; node++) {
//			boost::dynamic_bitset<> pa(graph.bsAdjMatrix[node]);
//			boost::dynamic_bitset<> clique(graph.n);
//			clique[node] = true;
//			while (pa.any()) {
//				// find of all neighbors the one with the most matching
//				// neighbor-nodes
//				unsigned int bestCard = 0;
//				int bestMatch = -1;
//				boost::dynamic_bitset<> tmpPA(pa);
//
//				for (boost::dynamic_bitset<>::size_type i = pa.find_first(); i
//						!= pa.npos; i = pa.find_next(i)) {
//					// operate on index i here
//
//					// calculate which nodes have the current clique and
//					// neighbors[i] in common:
//					tmpPA &= graph.bsAdjMatrix[i];
//					if (tmpPA.count() >= bestCard) {
//						bestMatch = i;
//						bestCard = tmpPA.count();
//					}
//
//					// reset tmpPA to pa
//					tmpPA |= pa;
//				}
//
//				clique[bestMatch] = true;
//
//				pa &= graph.bsAdjMatrix[bestMatch];
//				pa.set(bestMatch, false);
//			}
//
//			//clique contains the nodes of the found greatest clique
//			cliqueSize[node] = clique.count();
//
//			mean += cliqueSize[node];
//		}
//
//		mean /= graph.n;
//
//		int maxCliqueSize = 0;
//		int minCliqueSize = INT_MAX;
//		double tmpValue = 0;
//
//		for (int node = 0; node < graph.n; node++) {
//
//			if (cliqueSize[node] > maxCliqueSize)
//				maxCliqueSize = cliqueSize[node];
//
//			if (cliqueSize[node] < minCliqueSize)
//				minCliqueSize = cliqueSize[node];
//
//			// for sample standard deviation
//			tmpValue += (mean - cliqueSize[node]) * (mean - cliqueSize[node]);
//		}

		
	}

	//Degeneracy Feature
	private int computeDegeneracy(){
		return Toolkit.getDegeneracy(graph,null);
	}

	//Run Graph Coloring Algorithms ##############################################################################
	/*private void computeWelshPowell() {
		WelshPowell wp = new WelshPowell("color");
		wp.init(graph);
		double startTime = System.currentTimeMillis();
		wp.compute();
		double stopTime = System.currentTimeMillis();
		welshPowellRuntime = (stopTime - startTime)/ (double) 1000.0;
		welshPowellColor = wp.getChromaticNumber();
		//displayColoredGraph(welshPowellColor);
	}*/

	//Visualization procedures ###################################################################################
	/*private void displayColoredGraph(int color) {
		Color[] cols = new Color[color];
		for (int i = 0; i < color; i++) {
			cols[i] = Color.getHSBColor((float) (Math.random()), 0.8f, 0.9f);
		}
		for (Node n : graph) {
			int col = (int) n.getNumber("color");
			n.addAttribute("ui.style", "fill-color:rgba(" + cols[col].getRed() + ","
					+ cols[col].getGreen() + "," + cols[col].getBlue() + ",200);");
		}

		graph.display();
	}*/
}
